import * as THREE from "three";
import { Game } from "../Game";
import { Tile } from "./Tile";
import { Building, ResidentialZone } from "./buildings";
import { createBuilding } from "./buildings/BuildingFactory";
import { PowerService } from "./services/PowerService";
export class City extends THREE.Object3D {

    size: number;

    debugMeshes: THREE.Object3D<THREE.Object3DEventMap>;

    game: Game;
    root: THREE.Object3D<THREE.Object3DEventMap>;

    tiles: Tile[][];
    services: any[];
    vehicleGraph: any;

    simTime = 0;

    get population(): number {
        let population = 0;
        for (let x = 0; x < this.size; x++) {
            for (let y = 0; y < this.size; y++) {
                const tile = this.getTile(x, y);
                if (tile.building && tile.building instanceof ResidentialZone) {
                    population += tile.building.resident.count ?? 0;
                }
            }
        }
        return population;
    }

    constructor(game: Game) {

        super();

        this.game = game;
        this.name = "模拟城市";
        this.size = 10;

        this.debugMeshes = new THREE.Object3D();
        this.add(this.debugMeshes);

        this.root = new THREE.Object3D();
        this.add(this.root);

        this.tiles = [];

        this.add(new THREE.AmbientLight());

        this.services = [];
        this.services.push(new PowerService());

        // this.vehicleGraph = new VehicleGraph(this.size);
        // this.debugMeshes.add(this.vehicleGraph);

    }

    setSize(size: number) {
        this.size = size;

        this.init();
    }

    init() {
        for (let x = 0; x < this.size; x++) {
            const column = [];
            for (let y = 0; y < this.size; y++) {
                const tile = new Tile(x, y);
                tile.refreshView(this);
                this.root.add(tile);
                column.push(tile);
            }
            this.tiles.push(column);
        }

    }

    /** 返回网格坐标上的瓦片 */
    getTile(x: number, y: number) {

        if (x === undefined || y === undefined ||
            x < 0 || y < 0 ||
            x >= this.size || y >= this.size) {
            return null;
        } else {
            return this.tiles[x][y];
        }

    }

    /** 使用推土机推平建筑 */
    bulldoze(x: number, y: number) {

        const tile = this.getTile(x, y);

        if (tile.building) {
            // if (tile.building.type === Building.type.road) {
            //     this.vehicleGraph.updateTile(x, y, null);
            // }

            tile.building.dispose();
            tile.setBuilding(null);
            tile.refreshView(this);

            // Update neighboring tiles in case they needed to change their mesh (e.g. roads)
            this.getTile(x - 1, y)?.refreshView(this);
            this.getTile(x + 1, y)?.refreshView(this);
            this.getTile(x, y - 1)?.refreshView(this);
            this.getTile(x, y + 1)?.refreshView(this);
        }
    }

    placeBuilding(x: number, y: number, buildingType: string) {

        const tile = this.getTile(x, y);

        // If the tile doesnt' already have a building, place one there
        if (tile && !tile.building) {

            const building = createBuilding(buildingType);

            tile.setBuilding(building);
            tile.refreshView(this);

            // Update buildings on adjacent tile in case they needed to
            // change their mesh (e.g. roads)
            this.getTile(x - 1, y)?.refreshView(this);
            this.getTile(x + 1, y)?.refreshView(this);
            this.getTile(x, y - 1)?.refreshView(this);
            this.getTile(x, y + 1)?.refreshView(this);

            // if (tile.building.type === Building.type.road) {
            //     this.vehicleGraph.updateTile(x, y, tile.building);
            // }
        }
    }


    /**
     * Finds the first tile where the criteria are true
     */
    findTile(start: { x: number, y: number; }, filter: (tile: Tile) => (boolean), maxDistance: number): Tile {
        const startTile = this.getTile(start.x, start.y);
        const visited = new Set();
        const tilesToSearch = [];

        // Initialize our search with the starting tile
        tilesToSearch.push(startTile);

        while (tilesToSearch.length > 0) {
            const tile = tilesToSearch.shift();

            // Has this tile been visited? If so, ignore it and move on
            if (visited.has(tile.id)) {
                continue;
            } else {
                visited.add(tile.id);
            }

            // Check if tile is outside the search bounds
            const distance = startTile.distanceTo(tile);
            if (distance > maxDistance) continue;

            // Add this tiles neighbor's to the search list
            tilesToSearch.push(...this.getTileNeighbors(tile.x, tile.y));

            // If this tile passes the criteria 
            if (filter(tile)) {
                return tile;
            }
        }

        return null;
    }

    simulate(steps = 1) {
        let count = 0;
        while (count++ < steps) {
            // Update services
            this.services.forEach((service) => service.simulate(this));

            // Update each building
            for (let x = 0; x < this.size; x++) {
                for (let y = 0; y < this.size; y++) {
                    this.getTile(x, y).simulate(this);
                }
            }
        }
        this.simTime++;
    }

    draw() {

    }

    /**
     * Finds and returns the neighbors of this tile
    */
    getTileNeighbors(x: number, y: number) {
        const neighbors = [];

        if (x > 0) {
            neighbors.push(this.getTile(x - 1, y));
        }
        if (x < this.size - 1) {
            neighbors.push(this.getTile(x + 1, y));
        }
        if (y > 0) {
            neighbors.push(this.getTile(x, y - 1));
        }
        if (y < this.size - 1) {
            neighbors.push(this.getTile(x, y + 1));
        }

        return neighbors;
    }

}